#include "cpoprotocal.h"
#include "cpofsm.h"
#include "cpodata.h"
#include "es_print.h"
#include "cpolist.h"
#include "cpotimer.h"
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/msg.h>

extern int mq_id;
extern cpo_fsm_head_l *cpo_subfsm_hd;
extern cpo_fsm_t cpo_sysfsm;
extern cpo_dev_t *cpo_dev;
extern struct _subfsm_sn_t sf_sn;

int choose_ch_2close()
{
    printf("close one ch \n");
    ch_dev_t *ch_pos;
    int ch_sn = -1;
    unsigned int rt = 0;

    /* 1. 台选一台正在运行的、累计运行时间最长的冷机,  */
    list_for_each_entry(ch_pos, &cpo_dev->ch_head.list, list){
        if(ch_pos->ch_prop.is_run == RUNING){

            ES_PRT_DEBUG("For each ch: sn = %d, accl_rt = %d \n", ch_pos->sn, ch_pos->ch_prop.accl_rt);
            if(ch_pos->ch_prop.accl_rt >= rt){
                rt = ch_pos->ch_prop.accl_rt;
                ch_sn = ch_pos->sn;
            }
        }
    }

    /* 如果返回的ch_sn==-1, 说明所有冷机都不在运行 */
    if(ch_sn >= 0){
        ES_PRT_INFO("CPO: Select one ch[%d] to close, accl_rt = %d \n", ch_sn, ch_pos->ch_prop.accl_rt);
    }
    return ch_sn;
}

/* 台选一台将要打开的冷机, 并找到符合要求的冷冻水泵、冷却水泵、冷却塔 */
int choose_ch_2open()
{    
    point_prop_t *ch_pp;
    ch_dev_t *ch_pos;
    int ch_sn = -1;
    unsigned int rt = 4294967200;   // 为了找出最短运行时间设备而设的一个随机大值(相当于136 years)

    /* 1. 台选一台冷机 */
    list_for_each_entry(ch_pos, &cpo_dev->ch_head.list, list){
        /* 找一台不运行的 */
        if(ch_pos->ch_prop.is_run == !RUNING){
            /* 如果故障点位存在, 跳过那些有故障的 */
            ch_pp = find_point_from_devdp_bynm(CH_S, &ch_pos->ch_prop);
            if(ch_pp != NULL && ch_pp->val.b == true){
                continue;
            }
            ES_PRT_DEBUG("For each ch: sn = %d, accl_rt = %d \n", ch_pos->sn, ch_pos->ch_prop.accl_rt);
            /* 找到累计运行时间最小的 */
            if(ch_pos->ch_prop.accl_rt <= rt){
                rt = ch_pos->ch_prop.accl_rt;
                ch_sn = ch_pos->sn;
            }
        }
    }
    /* 结束台选 */
    if(ch_sn == -1){
        /* 设计院在规划阶段: 会根据当地使用环境, 配置足够多的冷机数量, 所以只要冷机不坏, 一般不会出现 total==running 的情况 */
        ES_PRT_ERROR("CPO: Can't find an useful ch to startup, total[%d]--running[%d] \n", cpo_dev->ch_head.ch_num, cpo_dev->ch_head.ch_on_cnt);
        return ErrNoExist;
    }

    /* 2. 确定冷冻、冷却水泵 */
    // TODO: 水泵的选择原则根据文档实现
    int cwp_sn = ch_sn;
    int pchwp_sn = ch_sn;

    /* 3. 确定冷却塔 */
    int ct_sn = ch_sn;

    sf_sn.ch_sn = ch_sn;
    sf_sn.pchwp_sn = pchwp_sn;
    sf_sn.cwp_sn = cwp_sn;
    sf_sn.ct_sn = ct_sn;

    cpo_fsm_t *subfsm_pos;
    list_for_each_entry(subfsm_pos, &cpo_subfsm_hd->list, list){
        if(subfsm_pos->fsmno == sf_sn.ch_sn){
            memcpy(&subfsm_pos->bind_dev, &sf_sn, sizeof(sf_sn));
        }
    }

    ES_PRT_INFO("CPO: Select a new subfsm: ch_sn = %d, accl_rt = %d, pchwp_sn = %d, cwp_sn = %d, ct_sn = %d \n", \
                ch_sn, pchwp_sn, cwp_sn, ct_sn, ch_pos->ch_prop.accl_rt);
    
    return ch_sn;
}

/* 处理系统状态机的事件 */
void cpo_event_sysfsm(cpo_msg_t *buf)
{
    ES_PRT_INFO("SYS fsm cur_state[%s], Recv SYS event[%d] \n", cpo_sysfsm.fsmset[cpo_sysfsm.cur_state].name, buf->fsmevt);

    cpo_sysfsm.fsmevt = buf->fsmevt;
    switch(buf->fsmevt){
        case EV_SYS_START:
            fsm_do(EV_SYS_START, &cpo_sysfsm);
            break;
        case EV_SYS_STOP:
            fsm_do(EV_SYS_STOP, &cpo_sysfsm);
            break;
        case EV_SYSPOINTS_ON:
            fsm_do(EV_SYSPOINTS_ON, &cpo_sysfsm);
            break;

        default:
            break;

    }
}

/* 处理子系统状态机的事件 */
void cpo_event_subfsm(cpo_msg_t *buf)
{
    cpo_fsm_t *pos;
#if 1
    if(buf->fsmevt == EV_SYS_STOP){
        /* 每个子状态机要一个个关 */

        /* 先关掉处于中间态的子状态机: 按照设计思路, 
        * 每启动一台冷机前都要等待上一个冷机完全启动, 
        * 所以同一时刻, 所有子状态机中应该最多只有一个子状态机
        * 处于中间态, 先stop这个状态机
        */
        list_for_each_entry(pos, &cpo_subfsm_hd->list, list){
            if(pos->cur_state != S_SUBSYS_IDLE && pos->cur_state != S_SUBSYS_INIT){
                ES_PRT_INFO("SUB fsm NO.[%d], cur_state[%s], Recv event[%d] \n", \
                            pos->fsmno, pos->fsmset[pos->cur_state].name, buf->fsmevt);
                pos->fsmevt = buf->fsmevt;
                fsm_do(buf->fsmevt, pos);
                return;
            }
        }

        /* 选一个idle态的状态机 */
        list_for_each_entry(pos, &cpo_subfsm_hd->list, list){
            if(pos->cur_state == S_SUBSYS_IDLE){
                pos->fsmevt = buf->fsmevt;
                fsm_do(buf->fsmevt, pos);
            }
        }
    }
#endif
    list_for_each_entry(pos, &cpo_subfsm_hd->list, list){
        if(pos->fsmno == buf->fsmno){
            ES_PRT_INFO("SUB fsm NO.[%d], cur_state[%s], Recv event[%d] \n", \
                        pos->fsmno, pos->fsmset[pos->cur_state].name, buf->fsmevt);

            pos->fsmevt = buf->fsmevt;
            fsm_do(buf->fsmevt, pos);
        }
    }
}

void cpo_event_proc(cpo_msg_t *buf)
{
    if(buf->fsmno == SYS_FSM){
        cpo_event_sysfsm(buf);
    }else{
        cpo_event_subfsm(buf);
    }
}

/* 接收事件，分类处理 */
void *cpo_event_thread(void *arg)
{
    cpo_msg_t rx_msg;

    int cnt;

    while(1){
        memset(&rx_msg, 0, sizeof(rx_msg));
        cnt = msgrcv(mq_id, &rx_msg, sizeof(rx_msg)-sizeof(rx_msg.mtype), CPO_MQTP_EVENT, 0);
        if(cnt < 0){
            ES_PRT_WARN("CPO: msgrcv in protocal thread errno: %s \n", strerror(errno));
        }

        cpo_event_proc(&rx_msg);
    }

}

/* 冷机加减机 */
void *cpo_ch_add_sub_thread(void *arg)
{
    cpo_fsm_t *subfsm_pos;
    ch_dev_t *ch_pos;
    data_type_u val1;
    data_type_u val2;
    point_prop_t *pp;

    bool state_flag;    // true: 可以继续台选标志

    int fsmno;

    cpo_dev->ch_head.add_flag = true;
    cpo_dev->ch_head.sub_flag = true;

    while(1){
        sleep(1);

        /* 等待有设备被添加 */
        if(cpo_dev->add_dev == false){
            continue;
        }

        /* 系统C-CALL==true, 才能进行加减机 */
        read_sysvp_byname(C_CALL, &val1);
        if(val1.b == false){
            continue;
        }

        /* 系统状态机在IDLE状态, 才能进行加减机 */
        if(cpo_sysfsm.cur_state != S_SYS_IDLE){
            continue;
        }

        state_flag = true;
        /* 所有的子状态机都处于init or idle 态才进行减机 */
        list_for_each_entry(subfsm_pos, &cpo_subfsm_hd->list, list){
            if(subfsm_pos->cur_state != S_SUBSYS_INIT && subfsm_pos->cur_state != S_SUBSYS_IDLE){
                state_flag = false;
                break;
            }
        }

        if(state_flag == false){
            continue;
        }

        /* 读取设定值 */
        read_sysvp_byname(CHW_TSP_V, &val1);
        /* 读取冷冻水出水温度 CHW-TSP, 此点位必须存在啊 */
        read_sysrp_byname("CHW-TSP", &val2);

        /* 冷冻水出水温度 >= (设定值+1), 才能进行加机 */
        if(val2.d >= val1.d+1){
            cpo_dev->ch_head.sub_tm = 0;
            cpo_dev->ch_head.add_flag = true;

            /* 至少有1台冷机在运行, 才进行台选 */
            state_flag = false;
            list_for_each_entry(ch_pos, &cpo_dev->ch_head.list, list){
                if(ch_pos->ch_prop.is_run == RUNING){
                    state_flag = true;
                    break;
                }
            }
            if(state_flag == false){
                continue;
            }

            /* 如果冷机负荷点位存在, 那么所有运行的冷机负荷都>=80%, 才能进行加机 */
            list_for_each_entry(ch_pos, &cpo_dev->ch_head.list, list){
                if(ch_pos->ch_prop.is_run == RUNING){
                    pp = find_point_from_devdp_bynm(CH_LOAD, &ch_pos->ch_prop);

                    /* 负荷点位存在, 我们默认所有冷机同时有负荷点位或者同时没有负荷点位 */
                    if(pp != NULL){
                        if(pp->val.d < 80.0){
                            cpo_dev->ch_head.add_flag = false;
                            cpo_dev->ch_head.add_tm = 0;
                            break;
                        }
                    }
                }
            }
            /* true: 说明冷机负荷点位都不存在, 或者点位存在, 且所有的负荷都>=80%, 满足加机条件 */
            if(cpo_dev->ch_head.add_flag == true){
                cpo_dev->ch_head.add_tm += 1;
            }else{
                cpo_dev->ch_head.add_tm = 0;
                continue;
            }

            /* 持续30min才进行台选 */
            if(cpo_dev->ch_head.add_tm >= T_ADD_CH_MIN){
                /* 台选一台冷机, 向该冷机的子状态机发送start事件 */
                fsmno = choose_ch_2open();
                if(fsmno >= 0){
                    send_event_2subfsm(EV_SUBSYS_START, fsmno);
                }
                cpo_dev->ch_head.add_tm = 0;
            }

        /* 出水温度 <= 设定值, 进行减机判断 */
        }else if(val2.d <= val1.d){
            int ch_runcnt = 0;
            bool ch_30load_cnt = false;
            bool ch_80load_cnt = true;
            cpo_dev->ch_head.add_tm = 0;

            /* 至少2台冷机在运行才能进行减机 */
            list_for_each_entry(ch_pos, &cpo_dev->ch_head.list, list){
                if(ch_pos->ch_prop.is_run == RUNING){
                    ch_runcnt++;
                    pp = find_point_from_devdp_bynm(CH_LOAD, &ch_pos->ch_prop);

                    /* 负荷点位存在, 我们默认所有冷机同时有负荷点位或者同时没有负荷点位 */
                    /* 减机的时候要求负荷点位必须存在 */
                    /* 至少1台冷机负荷<30.0, 其他所有负荷<80.0 才能减机 */
                    if(pp != NULL){
                        if(pp->val.d <= 30.0){
                            //ES_PRT_INFO("name %s, %s, %s, %lf \n", pp->name, pp->dev_key, pp->func, pp->val.d);
                            ch_30load_cnt = true;
                            continue;
                        }else if(pp->val.d >= 80.0){
                            //ES_PRT_INFO("name %s, %s, %s, %lf \n", pp->name, pp->dev_key, pp->func, pp->val.d);
                            ch_80load_cnt = false;
                        }
                    }
                }
            }
            if(ch_runcnt < 2 || ch_30load_cnt == false || ch_80load_cnt == false){
                cpo_dev->ch_head.sub_tm = 0;
                continue;
            }

            cpo_dev->ch_head.sub_tm++;
            if(cpo_dev->ch_head.sub_tm >= T_SUB_CH_MIN){
                fsmno = choose_ch_2close();
                if(fsmno >= 0){
                    send_event_2subfsm(EV_SUBSYS_STOP, fsmno);
                }
                cpo_dev->ch_head.sub_tm = 0;
            }

        }else{
            cpo_dev->ch_head.add_tm = 0;
            cpo_dev->ch_head.sub_tm = 0;
        }
    }
}